/**
 * Copyright 2009 Joe LaPenna
 */

package com.ruiyi.http;

import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.ParseException;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.scheme.SocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.EntityUtils;

//import util._Config;

import com.ruiyi.baseparser.Parser;
import com.ruiyi.baseparser.ParserUtil;
import com.ruiyi.baseparser.RuiyiType;
import com.ruiyi.error.RuiyiCredentialsException;
import com.ruiyi.error.RuiyiException;
import com.ruiyi.error.RuiyiParseException;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * @author Joe LaPenna (joe@joelapenna.com)
 */
abstract public class AbstractHttpApi implements HttpApi {
	protected static final Logger LOG = Logger.getLogger(AbstractHttpApi.class
			.getCanonicalName());

	private static final String DEFAULT_CLIENT_VERSION = "com.joelapenna.foursquare";
	private static final String CLIENT_VERSION_HEADER = "User-Agent";
	private static final int TIMEOUT = 60;

	private final DefaultHttpClient mHttpClient;
	private final String mClientVersion;

	public AbstractHttpApi(DefaultHttpClient httpClient, String clientVersion) {
		mHttpClient = httpClient;
		if (clientVersion != null) {
			mClientVersion = clientVersion;
		} else {
			mClientVersion = DEFAULT_CLIENT_VERSION;
		}
	}

	public RuiyiType executeHttpRequest(HttpRequestBase httpRequest,
			Parser<? extends RuiyiType> parser)
			throws RuiyiCredentialsException, RuiyiParseException,
			RuiyiException, IOException {

		HttpResponse response = executeHttpRequest(httpRequest);

		int statusCode = response.getStatusLine().getStatusCode();
		switch (statusCode) {
		case 200:
			String content = EntityUtils.toString(response.getEntity());
			return ParserUtil.consume(parser, content);

		case 400:
			throw new RuiyiException(response.getStatusLine().toString(),
					EntityUtils.toString(response.getEntity()));

		case 401:
			response.getEntity().consumeContent();
			throw new RuiyiCredentialsException(response.getStatusLine()
					.toString());

		case 404:
			response.getEntity().consumeContent();
			throw new RuiyiException(response.getStatusLine().toString());

		case 500:
			response.getEntity().consumeContent();
			throw new RuiyiException("Foursquare is down. Try again later.");

		default:
			response.getEntity().consumeContent();
			throw new RuiyiException("Error connecting to Ruiyi: " + statusCode
					+ ". Try again later.");
		}
	}

	public String doHttpPost(String url, NameValuePair... nameValuePairs)
			throws RuiyiCredentialsException, RuiyiParseException,
			RuiyiException, IOException {
		HttpPost httpPost = createHttpPost(url, nameValuePairs);

		HttpResponse response = executeHttpRequest(httpPost);

		switch (response.getStatusLine().getStatusCode()) {
		case 200:
			try {
				return EntityUtils.toString(response.getEntity());
			} catch (ParseException e) {
				throw new RuiyiParseException(e.getMessage());
			}

		case 401:
			response.getEntity().consumeContent();
			throw new RuiyiCredentialsException(response.getStatusLine()
					.toString());

		case 404:
			response.getEntity().consumeContent();
			throw new RuiyiException(response.getStatusLine().toString());

		default:
			response.getEntity().consumeContent();
			throw new RuiyiException(response.getStatusLine().toString());
		}
	}

	/**
	 * execute() an httpRequest catching exceptions and returning null instead.
	 * 
	 * @param httpRequest
	 * @return
	 * @throws IOException
	 */
	public HttpResponse executeHttpRequest(HttpRequestBase httpRequest)
			throws IOException {
		try {
			mHttpClient.getConnectionManager().closeExpiredConnections();
			return mHttpClient.execute(httpRequest);
		} catch (IOException e) {
			httpRequest.abort();
			throw e;
		}
	}

	public HttpGet createHttpGet(String url, NameValuePair... nameValuePairs) {
		String query = URLEncodedUtils.format(stripNulls(nameValuePairs),
				HTTP.UTF_8);
		HttpGet httpGet = new HttpGet(url + "?" + query);
		// HttpGet httpGet = new HttpGet(url);
		httpGet.addHeader(CLIENT_VERSION_HEADER, mClientVersion);
		return httpGet;
	}

	public HttpPost createHttpPost(String url, NameValuePair... nameValuePairs) {
		// String query = URLEncodedUtils.format(stripNulls(nameValuePairs),
		// HTTP.UTF_8);
		// HttpPost httpPost = new HttpPost(url + "?" + query);
		HttpPost httpPost = new HttpPost(url);
		httpPost.addHeader(CLIENT_VERSION_HEADER, mClientVersion);
		try {
			httpPost.setEntity(new UrlEncodedFormEntity(
					stripNulls(nameValuePairs), HTTP.UTF_8));
		} catch (UnsupportedEncodingException e1) {
			throw new IllegalArgumentException(
					"Unable to encode http parameters.");
		}
		return httpPost;
	}

	public HttpURLConnection createHttpURLConnectionPost(URL url,
			String boundary) throws IOException {
		HttpURLConnection conn = (HttpURLConnection) url.openConnection();
		conn.setDoInput(true);
		conn.setDoOutput(true);
		conn.setUseCaches(false);
		conn.setConnectTimeout(TIMEOUT * 1000);
		conn.setRequestMethod("POST");

		conn.setRequestProperty(CLIENT_VERSION_HEADER, mClientVersion);
		conn.setRequestProperty("Connection", "Keep-Alive");
		conn.setRequestProperty("Content-Type", "multipart/form-data;boundary="
				+ boundary);

		return conn;
	}

	private List<NameValuePair> stripNulls(NameValuePair... nameValuePairs) {
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		for (int i = 0; i < nameValuePairs.length; i++) {
			NameValuePair param = nameValuePairs[i];
			if (param.getValue() != null) {
				params.add(param);
			}
		}
		return params;
	}

	/**
	 * Create a thread-safe client. This client does not do redirecting, to
	 * allow us to capture correct "error" codes.
	 * 
	 * @return HttpClient
	 */
	public static final DefaultHttpClient createHttpClient() {
		// Sets up the http part of the service.
		final SchemeRegistry supportedSchemes = new SchemeRegistry();

		// Register the "http" protocol scheme, it is required
		// by the default operator to look up socket factories.
		final SocketFactory sf = PlainSocketFactory.getSocketFactory();
		supportedSchemes.register(new Scheme("http", sf, 80));
		supportedSchemes.register(new Scheme("https", SSLSocketFactory
				.getSocketFactory(), 443));

		// Set some client http client parameter defaults.
		final HttpParams httpParams = createHttpParams();
		HttpClientParams.setRedirecting(httpParams, false);

		final ClientConnectionManager ccm = new ThreadSafeClientConnManager(
				httpParams, supportedSchemes);
		return new DefaultHttpClient(ccm, httpParams);
	}

	/**
	 * Create the default HTTP protocol parameters.
	 */
	private static final HttpParams createHttpParams() {
		final HttpParams params = new BasicHttpParams();

		// Turn off stale checking. Our connections break all the time anyway,
		// and it's not worth it to pay the penalty of checking every time.
		HttpConnectionParams.setStaleCheckingEnabled(params, false);

		HttpConnectionParams.setConnectionTimeout(params, TIMEOUT * 1000);
		HttpConnectionParams.setSoTimeout(params, TIMEOUT * 1000);
		HttpConnectionParams.setSocketBufferSize(params, 8192);

		return params;
	}
}
